feat(lint,cli): flag flow update_record writes to readonly fields at design time (#3425)#3465
Merged
Merged
Conversation
…design time (#3425) A flow `update_record` node that writes a field the target object declares `readonly: true`, under the default `runAs: 'user'`, is a silent no-op: the objectql engine strips static-`readonly` fields from a non-system UPDATE payload (#2948) so the write never lands, while the step still reports success. #3407/#3413 surfaced the strip at run time; this shifts discovery left to `os validate` / `os build`. - New `@objectstack/lint` rule `validateReadonlyFlowWrites`: static readonly + literal field under runAs!=='system' → error (gates); readonlyWhen → warning. Skips create_record (INSERT is engine-exempt), runAs:'system' flows, templated object names, and non-literal fields maps to stay false-positive-free. - Wired into `os validate` and `os compile`/`os build` (mirrors the security posture gate). Verified: reds on a violating app-crm flow, clean on all example apps. - Documents the formal contract in the objectstack-data / -automation skills: readonly governs the user/API surface; system writers (runAs:'system', hooks, seeds) maintain it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes the design-time half of #3425 (D1 + D2 from the triage plan).
A flow
update_recordnode that writes a field the target object declaresreadonly: true, under the defaultrunAs: 'user', is a silent no-op: the objectql engine strips static-readonlyfields from a non-system UPDATE payload (#2948), so the intended write never lands — yet the step still reportssuccess. #3407/#3413 made the strip observable at run time (step warning +droppedFields); this PR shifts discovery left toos validate/os build, so an author finds the mismatch at design time instead of by reading server WARN logs days later.Decision recorded on the issue
readonlysemantics are kept as-is and formalized, not loosened to exempt the flow engine (that would turn any user-triggerable flow into a readonly-bypass gadget and undo #2948). The formal contract:readonlyreadonlyWhenrunAs:'user'flow (default)runAs:'system'flow, system hooks, seeds"Users can't edit this, but automation maintains it" = declare the field
readonlyand run the maintaining flowrunAs:'system'.Changes
validateReadonlyFlowWrites(@objectstack/lint) — pure(stack) => Finding[](ADR-0019). Staticreadonly+ literal field underrunAs !== 'system'→ error (gates);readonlyWhen→ warning (advisory). Deliberately narrow to be false-positive-free: skipscreate_record(INSERT is engine-exempt from the strip),runAs:'system'flows, templated object names, and non-literalfieldsmaps.os validateandos compile/os build, mirroring the security-posture gate (errors fail; advisories print dimmed).runAs:'system'" guidance added to theobjectstack-dataandobjectstack-automationskills.Verification
@objectstack/lintsuite green (283); DTS build clean; edited CLI files typecheck clean.app-crm,app-showcase,app-todo(os validatepasses).crm_convert_lead_wizard(runAs:'user')update_recordatcrm_opportunity.approval_statusmakesos validatefail withReadonly flow-write check failed … flows[0].nodes[6].config.fields.approval_status(reverted).Out of scope (tracked separately)
create_recordfailure error surfacing as"[object Object]"— split to automation: step error objects can surface as "[object Object]" in run-history consumers (split from #3425) #3450.🤖 Generated with Claude Code